classdef NMRGyroscope < handle
    %NMRGYROSCOPE implement a series of measurements of NMRG
    
    properties
        ziMaster
        ziSlave
        pumpLaser
        probeLaser
        
        wavelengthMeter
        pumpCh
        probeCh
        monitor
        
        tek
        tc
        sim960
        
        aom
        eater
        bx
        by
        by129
        by131
        bz0
        bz1
        pmp
        prb
        
        rbSignal
        bTrans
        bTransDCx
        bTransDCy
        Xe129
        Xe131
        
        SystemInfo
        Job
    end
    
    methods
        function obj = NMRGyroscope(varargin)
            L = log4m.getLogger;
            L.info('NMRGyroscope::NMRGyroscope', 'A NMRGyroscope object is created.');
        end
        
        function disp(obj)
            if ~isempty(obj.SystemInfo.MainField)
                fprintf('Main Field Information:\n\n');
                disp(struct2table(obj.SystemInfo.MainField));
            end
            
            if ~isempty(obj.SystemInfo.WorkingPoint)
                fprintf('WorkingPoint Information:\n\n');
                disp(struct2table(obj.SystemInfo.WorkingPoint));
            end 
            
            if ~isempty(obj.SystemInfo.TransFieldLocking)
                fprintf('TransFieldLocking Information:\n\n');
                disp(struct2table(obj.SystemInfo.TransFieldLocking));
            end
            
            if ~isempty(obj.SystemInfo.MagnetometorSensitivity)
                fprintf('MagnetometerSensitivity Information:\n\n');
                disp(struct2table(obj.SystemInfo.MagnetometorSensitivity));
            end
            
            if ~isempty(obj.SystemInfo.MagnetometerBandwidth)
                fprintf('MagnetometerBandwidth Information:\n\n');
                disp(struct2table(obj.SystemInfo.MagnetometerBandwidth));
            end
           
            if ~isempty(obj.SystemInfo.Xe129FID)
                format long;
                fprintf('Xe129FID Information:\n\n');
                disp(struct2table(obj.SystemInfo.Xe129FID));
                format short;
            end
            
            if ~isempty(obj.SystemInfo.Xe131FID)
                format long;
                fprintf('Xe131FID Information:\n\n');
                disp(struct2table(obj.SystemInfo.Xe131FID));
                format short;
            end
        end
    end
    
    %%
    methods
        function obj = initialize(obj, varargin)
            import MeasurementComponent.Actuator.* MeasurementComponent.Detector.*
            import MeasurementComponent.Signal.*
            L = log4m.getLogger;

            p = inputParser;
            p.addParameter('master', 'dev3543', @ischar);
            p.addParameter('slave', 'dev3560', @ischar);
            p.addParameter('pumpCh', 2, @(x) ismember(x, 1:4) );
            p.addParameter('probeCh', 3, @(x) ismember(x, 1:4) );
            p.parse(varargin{:});            
            
            L.info('NMRGyroscope::initialize', 'Initializing NMRGyroscope.');
            
            % clear all system information if exists
            sysinfo.MainField = [];
            sysinfo.WorkingPoint = [];
            sysinfo.TransFieldLocking = [];
            sysinfo.MagnetometorSensitivity = [];
            sysinfo.MagnetometerBandwidth = [];
            sysinfo.Xe129FID = [];
            sysinfo.Xe131FID = [];
            sysinfo.Xe129Relaxation = [];
            sysinfo.Xe131Relaxation = [];
            sysinfo.Xe129T1Rho = [];
            sysinfo.Xe131T1Rho = [];
            sysinfo.XeDriving = [];
            sysinfo.XePhaseNoise = [];
        
            obj.SystemInfo = sysinfo;
            
            % get handles of core devices
            getSession;
            obj.ziMaster = sess.getDevice(p.Results.master); obj.ziMaster.setAlias('ziMaster');
            obj.ziSlave = sess.getDevice(p.Results.slave); obj.ziSlave.setAlias('ziSlave');
            obj.tc = sess.getDevice('YuDian'); obj.tc.connect_device();
            obj.sim960 = sess.getDevice('SIM960');
            obj.ConnectWavelengthMeter(p.Results.pumpCh, p.Results.probeCh);
            
            obj.monitor = Table1.SystemMonitor.getMonitor(); obj.monitor.setSystem(obj);

            L.info('NMRGyroscope::initialize', 'Loading Settings to ZI devices ...');
            obj.ziMaster.loadSettings([Constant.ZISettingsPath '\settingsMaster.xml']);
            obj.ziSlave.loadSettings([Constant.ZISettingsPath '\settingsSlave.xml']); fprintf('\n');

%             obj.tek = sess.getDevice('tek');
%             obj.pumpLaser = sess.getDevice('toptica64'); 
%             obj.probeLaser = sess.getDevice('toptica82');
            
            % Creating Actuators
            obj.aom = AOM('AOM1');
            obj.eater = Eater('Eater1');
            obj.bx  = Coil('X5');
            obj.by  = Coil('Y9');
            obj.by129  = Coil('Y9');
            obj.by131  = Coil('Y9');
            obj.bz0 = Coil('Z8a');
            obj.bz1 = Coil('Z4');
            L.info('NMRGyroscope::initialize', 'Actuators created.'); fprintf('\n');
            
            % Creating Detectors
            obj.pmp = PhotoDetector('PumpTransmission');
            obj.prb = BalancedPhotoDetector('ProbeFaradayRotation');
            L.info('NMRGyroscope::initialize', 'Detector created.'); fprintf('\n');
            
            % Connections
            obj.aom.setVfController( obj.ziMaster.AuxOutPort(3) ).setVf(); % using default Vf defined in Actuators.xml
            obj.aom.connect_to( obj.ziSlave.AuxOutPort(2) );
            obj.eater.connect_to( obj.ziSlave.AuxOutPort(4) );
            obj.bx.connect_to(  obj.ziSlave.AuxOutPort(1) );
            obj.by.connect_to(  obj.ziSlave.SigOutPort() );
            obj.by129.connect_to(  obj.ziSlave.SigOutPort() );
            obj.by131.connect_to(  obj.ziSlave.SigOutPort() );
            obj.bz0.connect_to( obj.ziSlave.AuxOutPort(3) );
            obj.bz1.connect_to( obj.ziMaster.SigOutPort() );
            
            obj.prb.connect_to( obj.ziMaster.SigInPort() );
            obj.pmp.connect_to( obj.ziMaster.AuxInPort(1) );
            L.info('NMRGyroscope::initialize', 'Connections set.'); fprintf('\n');
            
            % Configure
            obj.bx.setDriver( obj.ziSlave.LockInChannel(1) );
            obj.by.setDriver( obj.ziSlave.LockInChannel(4).setOscillator(4) );
            obj.by129.setDriver( obj.ziSlave.LockInChannel(1).setOscillator(1) );
            obj.by131.setDriver( obj.ziSlave.LockInChannel(2).setOscillator(2) );
            obj.bz0.setDriver( obj.ziSlave.LockInChannel(3) );
            obj.bz1.setDriver( obj.ziMaster.LockInChannel(1) );
            L.info('NMRGyroscope::initialize', 'Drivers Configured.'); fprintf('\n');
            
            % Signal
            obj.rbSignal  = Signal('rbSignal',   obj.ziMaster.LockInChannel(1));
            obj.bTrans    = Signal('bTrans',     obj.ziMaster.LockInChannel(2));
            obj.bTransDCx = Signal('bTransDCx',  obj.ziMaster.LockInChannel(3));
            obj.bTransDCy = Signal('bTransDCy',  obj.ziMaster.LockInChannel(4));
            obj.Xe129     = Signal('Xe129',      obj.ziSlave.LockInChannel(3) );
            obj.Xe131     = Signal('Xe131',      obj.ziSlave.LockInChannel(4) );
            L.info('NMRGyroscope::initialize', 'Signal Channels Defined.'); fprintf('\n');
            
            % Jobs
            job.GetRFSpectrum = Table1.NMRGJobs.GetRFSpectrum(obj);
            job.GetWorkingPoint = Table1.NMRGJobs.GetWorkingPoint(obj);
            job.GetMagnetometerSensitivity = Table1.NMRGJobs.GetMagnetometerSensitivity(obj);
            job.GetRFMagnetometerSensitivity = Table1.NMRGJobs.GetRFMagnetometerSensitivity(obj);
            job.GetMagnetometerBandwidth = Table1.NMRGJobs.GetMagnetometerBandwidth(obj);
            job.GetXe129FID = Table1.NMRGJobs.GetXe129FID(obj);
            job.GetXe131FID = Table1.NMRGJobs.GetXe131FID(obj);
            job.GetXe129T1Rho = Table1.NMRGJobs.GetXe129T1Rho(obj);
            job.GetXe131T1Rho = Table1.NMRGJobs.GetXe131T1Rho(obj);
            
            obj.Job = job;
        end
        
        function obj = setRFMagnetometer(obj, varargin)
            import MeasurementComponent.Signal.*
            L = log4m.getLogger;
            
            p=inputParser;
            p.addParameter('field', 6.0, @(x) x>0);
            p.addParameter('frequency', 100e3, @(x) x>0);
            p.addParameter('amplitude', 0.1, @(x) x>0);
            p.addParameter('driverChannel', 4, @Miscellaneous.isZIindex4);
            p.addParameter('signalOscillator', 1, @Miscellaneous.isZIindex4);
            p.addParameter('order', 4, @(x) ismember(x, 1:8));
            p.addParameter('bandwidth', 100, @(x) x>0);
            p.parse(varargin{:});
            
            % prepare environment
            obj.UnlockTransverseField();
            obj.ziMaster.disableOutAmplitude();
            obj.ziSlave.disableOutAmplitude();
            obj.ziMaster.enableSigInAC();
            obj.bTrans.diableAuxOutput(4);
            obj.bTransDCx.diableAuxOutput(1);
            obj.bTransDCy.diableAuxOutput(2);
            
            % setup driving fields
            obj.bz0.setDC(p.Results.field);
            obj.by.setDriver( obj.ziSlave.LockInChannel(p.Results.driverChannel).setOscillator(p.Results.driverChannel) );
            obj.by.setAC('frequency', p.Results.frequency, 'amplitude', p.Results.amplitude, 'offset', 0.0, 'range', Miscellaneous.ziFindOutputRange(p.Results.amplitude, false)); pause(0.5);
            
            % setup signal            
            obj.rbSignal.setupLIAchannel('extRefStatus',   false, ...
                                     'extRefAutoMode', -1, ...
                                     'oscSelect',      p.Results.signalOscillator, ...
                                     'oscFrequency',   p.Results.frequency, ...
                                     'harmonics',      1, ...
                                     'phase',          0.0, ...
                                     'signal',         0, ...
                                     'order',          p.Results.order, ...
                                     'bandwidth',      p.Results.bandwidth, ...
                                     'isSinc',         false, ...
                                     'transferStatus', true, ...
                                     'transferRate',   10*p.Results.bandwidth, ...
                                     'triggerMode',    0, ...
                                     'amplitudeStatus',false, ...
                                     'amplitude',      0.0 ...
                                     );
            L.info('NMRGyroscope::setRFMagnetometer', sprintf('RbSignal Demod Order = [%d], Bandwidth = [%3.2f]', p.Results.order, p.Results.bandwidth));
            
            phase0 = obj.rbSignal.autoZeroPhase();
            L.info('NMRGyroscope::setRFMagnetometer', sprintf('Demod Phase is set to [%5.3f]', phase0));
        end
        
        function obj = setParametricMagnetometer(obj, varargin)
            import MeasurementComponent.Signal.*
            L = log4m.getLogger;
            
            p = inputParser;
            p.addParameter('frequencyZ', 100e3, @(x) x>0);
            p.addParameter('amplitudeZ', 3.0, @(x) x>0 && x<5);
            p.addParameter('initialBx0', 0.0, @(x) x>= -3.0 && x <= 3.0);
            p.addParameter('initialBy0', 0.0, @(x) x>= -3.0 && x <= 3.0);
            p.addParameter('transDCscale', 1000, @(x) x>=0);
            p.addParameter('transACscale', 100, @(x) x>=0);
            p.addParameter('orderAC', 4, @(x) ismember(x, 1:8));
            p.addParameter('orderDC', 8, @(x) ismember(x, 1:8));
            p.addParameter('bandwidthAC', 1e3, @(x) x>0);
            p.addParameter('bandwidthDC', 10, @(x) x>0);
            p.addParameter('isLockTransverseField', false, @islogical);
            p.parse(varargin{:});

            % prepare environment
            obj.UnlockTransverseField();
            obj.ziMaster.disableOutAmplitude();
            obj.ziSlave.disableOutAmplitude();
            obj.ziMaster.enableSigInAC();
            obj.ziSlave.enableSigInAC();
            
            % setup driving fields
            obj.bz1.setAC('frequency', p.Results.frequencyZ, 'amplitude', p.Results.amplitudeZ);
            
            if ~isempty(obj.SystemInfo.WorkingPoint)
                bx0 = obj.SystemInfo.WorkingPoint.biasX;
                by0 = obj.SystemInfo.WorkingPoint.biasY;
                phaseX = obj.SystemInfo.WorkingPoint.phaseX;
                phaseY = obj.SystemInfo.WorkingPoint.phaseY;
            else
                bx0 = p.Results.initialBx0;
                by0 = p.Results.initialBy0;
                phaseX = 0.0;
                phaseY = 0.0;
            end
            obj.bx.setDC(bx0); 
            obj.by.setDC(by0);
            
            % setup signal
            obj.bTrans.referencedBy(obj.bz1);
            obj.bTrans.setupLIAchannel('harmonics',      1, ...
                                       'phase',          phaseY, ...
                                       'signal',         0, ...
                                       'order',          p.Results.orderAC, ...
                                       'bandwidth',      p.Results.bandwidthAC, ...
                                       'isSinc',         false, ...
                                       'transferStatus', true, ...
                                       'transferRate',   10*p.Results.bandwidthAC, ...
                                       'triggerMode',    0, ...
                                       'amplitudeStatus',false, ...
                                       'amplitude',      0.0 ...
                                       );
                                   
            obj.bTransDCx.referencedBy(obj.bz1);
            obj.bTransDCx.setupLIAchannel('harmonics',      1, ...
                                          'phase',          phaseY, ...
                                          'signal',         0, ...
                                          'order',          p.Results.orderDC, ...
                                          'bandwidth',      p.Results.bandwidthDC, ...
                                          'isSinc',         false, ...
                                          'transferStatus', true, ...
                                          'transferRate',   10*p.Results.bandwidthDC, ...
                                          'triggerMode',    0, ...
                                          'amplitudeStatus',false, ...
                                          'amplitude',      0.0 ...
                                          );

            obj.bTransDCy.referencedBy(obj.bz1);
            obj.bTransDCy.setupLIAchannel('harmonics',      1, ...
                                          'phase',          phaseX, ...
                                          'signal',         0, ...
                                          'order',          p.Results.orderDC, ...
                                          'bandwidth',      p.Results.bandwidthDC, ...
                                          'isSinc',         false, ...
                                          'transferStatus', true, ...
                                          'transferRate',   10*p.Results.bandwidthDC, ...
                                          'triggerMode',    0, ...
                                          'amplitudeStatus',false, ...
                                          'amplitude',      0.0 ...
                                          );
            
            % signal output
            obj.bTrans.export_to_AuxOut(   4, ziAuxOutSignal.DemodX, 'preoffset', 0.0, 'scale', p.Results.transACscale, 'offset', 0.0, 'limitlower', -2.5, 'limitupper', 2.5);
            obj.bTransDCx.export_to_AuxOut(1, ziAuxOutSignal.DemodX, 'preoffset', 0.0, 'scale', p.Results.transDCscale, 'offset', 0.0, 'limitlower', -9.5, 'limitupper', 9.5);
            obj.bTransDCy.export_to_AuxOut(2, ziAuxOutSignal.DemodX, 'preoffset', 0.0, 'scale', p.Results.transDCscale, 'offset', 0.0, 'limitlower', -9.5, 'limitupper', 9.5);

            if p.Results.isLockTransverseField
                obj.LockTransverseField();
                L.info('NMRGyroscope::setParametricMagnetometer', 'Set to ParametricMagnetometer mode. Locked to previously determind WorkingPoint.');
            else
                L.info('NMRGyroscope::setParametricMagnetometer', 'Transverse field not locked.');
            end
        end
        
        function obj = setNMR(obj, varargin)
            import MeasurementComponent.Signal.*
            L = log4m.getLogger;

            try
                f129 = obj.SystemInfo.Xe129FID.f129;
                f131 = obj.SystemInfo.Xe131FID.f131;
            catch
                f129 = [];
                f131 = [];
                L.info('NMRGyroscope::setNMR', 'Cannoy get Xe resonance frequencies. Using guess values.');
            end
            
            try
                phase129 = -obj.SystemInfo.MagnetometerBandwidth.phase129;
                phase131 = -obj.SystemInfo.MagnetometerBandwidth.phase131;
            catch
                phase129 = 0.0;
                phase131 = 0.0;
            end
            
            p=inputParser;
            p.addParameter('frequency129', f129, @(x) x>0 || isempty(x));
            p.addParameter('frequency131', f131, @(x) x>0 || isempty(x));
            p.addParameter('amplitude129', 0.050, @(x) x>0);
            p.addParameter('amplitude131', 0.500, @(x) x>0);
            p.addParameter('phase129',     phase129, @(x) x>=0 && x<=360);
            p.addParameter('phase131',     phase131, @(x) x>=0 && x<=360);
            p.addParameter('order129',     4,     @(x) ismember(x, 1:8));
            p.addParameter('order131',     4,     @(x) ismember(x, 1:8));
            p.addParameter('bandwidth129', 10.19, @(x) x>0);
            p.addParameter('bandwidth131', 10.19, @(x) x>0);
            p.parse(varargin{:});
            
            if isempty(p.Results.frequency129) || isempty(p.Results.frequency131)
                error('Cannot get Xe frequencies.');
            end
            
            L.info('NMRGyroscope::setNMR', 'Initializing NMR setup.');
            driver129 = obj.ziSlave.LockInChannel(1); 
            driver129.setup('extRefStatus',   false, ...
                            'extRefAutoMode', -1, ...
                            'oscSelect',      1, ...
                            'oscFrequency',   p.Results.frequency129, ...
                            'harmonics',      1, ...
                            'phase',          0.0, ...
                            'signal',         174, ...
                            'order',          1, ...
                            'bandwidth',      10e3, ...
                            'isSinc',         false, ...
                            'transferStatus', false, ...
                            'transferRate',   100, ...
                            'triggerMode',    0, ...
                            'amplitudeStatus',false, ...
                            'amplitude',      p.Results.amplitude129 ...
                            );
            driver131 = obj.ziSlave.LockInChannel(2);
            driver131.setup('extRefStatus',   false, ...
                            'extRefAutoMode', -1, ...
                            'oscSelect',      2, ...
                            'oscFrequency',   p.Results.frequency131, ...
                            'harmonics',      1, ...
                            'phase',          0.0, ...
                            'signal',         174, ...
                            'order',          1, ...
                            'bandwidth',      10e3, ...
                            'isSinc',         false, ...
                            'transferStatus', false, ...
                            'transferRate',   100, ...
                            'triggerMode',    0, ...
                            'amplitudeStatus',false, ...
                            'amplitude',      p.Results.amplitude131 ...
                            );
            obj.Xe129.setupLIAchannel('extRefStatus',   false, ...
                            'extRefAutoMode', -1, ...
                            'oscSelect',      1, ...
                            'oscFrequency',   p.Results.frequency129, ...
                            'harmonics',      1, ...
                            'phase',          p.Results.phase129, ...
                            'signal',         0, ...
                            'order',          p.Results.order129, ...
                            'bandwidth',      p.Results.bandwidth129, ...
                            'isSinc',         false, ...
                            'transferStatus', true, ...
                            'transferRate',   10*p.Results.bandwidth129, ...
                            'triggerMode',    0, ...
                            'amplitudeStatus',false, ...
                            'amplitude',      0.0 ...
                            );

            obj.Xe131.setupLIAchannel('extRefStatus',   false, ...
                            'extRefAutoMode', -1, ...
                            'oscSelect',      2, ...
                            'oscFrequency',   p.Results.frequency131, ...
                            'harmonics',      1, ...
                            'phase',          p.Results.phase131, ...
                            'signal',         0, ...
                            'order',          p.Results.order131, ...
                            'bandwidth',      p.Results.bandwidth131, ...
                            'isSinc',         false, ...
                            'transferStatus', true, ...
                            'transferRate',   10*p.Results.bandwidth131, ...
                            'triggerMode',    0, ...
                            'amplitudeStatus',false, ...
                            'amplitude',      0.0 ...
                            );
            L.info('NMRGyroscope::setNMR', 'Initialization Completed.');
        end
    end
    
    %%
    methods
        function ConnectWavelengthMeter(obj, pumpCh, probeCh)
            obj.wavelengthMeter = EquipmentControl.WavelengthMeter.WavelengthMeterClient.getClient('192.168.1.253', 54321);
            obj.wavelengthMeter.restart();
            try
                obj.wavelengthMeter.startBackgroundMonitor(pumpCh);
                obj.wavelengthMeter.startBackgroundMonitor(probeCh);
            catch
                %
            end
            obj.pumpCh = pumpCh;
            obj.probeCh = probeCh;
        end
        
        function [res, ts] = getPumpPower(obj)
            ts = datetime('now');
            res = obj.sim960.getMeasureInput();
        end
        function [res, ts] = getCellTemperature(obj)
            ts = datetime('now');
            res = obj.tc.getMeasuredTemperature(true);%isSilence = true;
        end
        function [res, ts] = getPumpFrequency(obj)
            if ~isempty(obj.pumpCh)
                [res, ts] = obj.wavelengthMeter.GetFrequency_async(obj.pumpCh);
            else
                res = [];
                ts = NaT;
            end
        end
        function [res, ts] = getProbeFrequency(obj)
            if ~isempty(obj.probeCh)
                [res, ts] = obj.wavelengthMeter.GetFrequency_async(obj.probeCh);
            else
                res = [];
                ts = NaT;
            end
        end

        function obj = startMonitor(obj)
            obj.monitor.start();
        end
        function obj = stopMonitor(obj)
            obj.monitor.stop();
        end
        function record = getMonitorRecord(obj)
            record = obj.monitor.record;
        end
    end
    methods
        res = GetMainField(obj, varargin)
        fig = PlotMainField(obj, data)
        fit = FitMainField(obj, signal, varargin)
        
        res = GetWorkingPoint(obj,varargin)
        fig = PlotWorkingPoint(obj, data)
        fit = FitWorkingPoint(obj, signal, varargin)
                
        obj = GetMagnetometerSensitivity(obj, varargin)
        fig = PlotMagnetometerSensitivity(obj, data)
        fit = FitMagnetometerSensitivity(obj, signal, varargin)
        
        res = GetMagnetometerBandwidth(obj, varargin)
        fig = PlotMagnetometerBandwidth(obj, data)
        fit = FitMagnetometerBandwidth(obj, signal, varargin)
        
        res = GetXe129FID(obj, duration, varargin)
        fig = PlotXe129FID(obj, data)
        fit = FitXe129FID(obj, signal, varargin)
        
        res = GetXe131FID(obj, duration, varargin)
        fig = PlotXe131FID(obj, data)
        fit = FitXe131FID(obj, signal, varargin)
        
        res = GetXeDriving(obj, varargin)
        fig = PlotXeDriving(obj, data)
        
        res = GetXePhaseNoise(obj, varargin)
        fig = PlotXePhaseNoise(obj, spectrumData)
        fit = FitXePhaseNoise(obj, signal, varargin)

        res = GetXe129T1Rho(obj, duration, varargin)
        fig = PlotXe129T1Rho(obj, data, sign)
        fit = FitXe129T1Rho(obj, signal, singalPump, varargin)

        res = GetXe131T1Rho(obj, duration, varargin)
        fig = PlotXe131T1Rho(obj, data, sign)
        fit = FitXe131T1Rho(obj, signal, varargin)

        res = GetXeRelaxation(obj, duration, varargin)
        fig = PlotXeRelaxation(obj, data)
        
        res = GetXe129Rabi_Ex(obj, duration, durationDecay, varargin)
        
        res = GroupCharacterization(obj, varargin)         
%         res = GroupRbSpinPolarization(obj, aomVoltageList, varargin)
%         fig = PlotRbSpinPolarization(~, polarizationData, field2freqCoeff)
    end
    
    methods
        obj = LockTransverseField(obj, varargin)
        obj = UnlockTransverseField(obj, channel)
        
        obj = LockXe129(obj)
        obj = UnlockXe129(obj)
        obj = UnlockXe131(obj)
%         obj = LockXe131(obj, varargin)
    end
end

